<!doctype html>
<html lang="zh-CN" data-theme="light">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="generator" content="VuePress 2.0.0-beta.68" />
    <meta name="theme" content="VuePress Theme Hope 2.0.0-beta.250" />
    <style>
      html {
        background: #fff;
      }

      html[data-theme="dark"] {
        background: #1d1e1f;
      }

      body {
        background: var(--bg-color);
      }
    </style>
    <script>
      const userMode = localStorage.getItem("vuepress-theme-hope-scheme");
      const systemDarkMode =
        window.matchMedia &&
        window.matchMedia("(prefers-color-scheme: dark)").matches;

      if (userMode === "dark" || (userMode !== "light" && systemDarkMode)) {
        document.documentElement.setAttribute("data-theme", "dark");
      }
    </script>
    <meta name="referrer" content="no-referrer"><link rel="icon" href="https://gitee.com/eddie-lucas/images/raw/master/img/logo.jpg"><link rel="icon" href="https://gitee.com/eddie-lucas/images/raw/master/img/logo.png" type="image/png" sizes="512x512"><link rel="icon" href="https://gitee.com/eddie-lucas/images/raw/master/img/logo.png" type="image/png" sizes="192x192"><link rel="manifest" href="/manifest.webmanifest" crossorigin="use-credentials"><meta name="theme-color" content="#46bd87"><link rel="apple-touch-icon" href="https://gitee.com/eddie-lucas/images/raw/master/img/logo.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="msapplication-TileImage" content="https://gitee.com/eddie-lucas/images/raw/master/img/logo.png"><meta name="msapplication-TileColor" content="#ffffff"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"><title>MyBatis常见问题 | 小刘Learning</title><meta name="description" content="">
    <link rel="preload" href="/assets/style-3ede7958.css" as="style"><link rel="stylesheet" href="/assets/style-3ede7958.css">
    <link rel="modulepreload" href="/assets/app-d83e1369.js"><link rel="modulepreload" href="/assets/MyBatis常见问题.html-3782c8f7.js"><link rel="modulepreload" href="/assets/MyBatis常见问题.html-13559d56.js"><link rel="modulepreload" href="/assets/plugin-vue_export-helper-c27b6911.js">
    <link rel="prefetch" href="/assets/aboutMe.html-72f014c7.js" as="script"><link rel="prefetch" href="/assets/index.html-69cd5f22.js" as="script"><link rel="prefetch" href="/assets/Java集合.html-f6d1e995.js" as="script"><link rel="prefetch" href="/assets/InnoDB的Buffer Pool.html-f0ae2755.js" as="script"><link rel="prefetch" href="/assets/InnoDB的行格式和页结构.html-31890643.js" as="script"><link rel="prefetch" href="/assets/MySQL中的各种锁.html-b819061d.js" as="script"><link rel="prefetch" href="/assets/MySQL中的排序.html-5442bdf2.js" as="script"><link rel="prefetch" href="/assets/MySQL事务与MVCC.html-11dea9ac.js" as="script"><link rel="prefetch" href="/assets/MySQL优化器原理.html-44cdf1f3.js" as="script"><link rel="prefetch" href="/assets/5种IO模型.html-623f6d24.js" as="script"><link rel="prefetch" href="/assets/Netty的线程模型.html-1933240c.js" as="script"><link rel="prefetch" href="/assets/粘包拆包问题.html-e5c58280.js" as="script"><link rel="prefetch" href="/assets/AOF和RDB持久化.html-de83ad53.js" as="script"><link rel="prefetch" href="/assets/Gossip协议.html-9cc1d704.js" as="script"><link rel="prefetch" href="/assets/Redisson看门狗机制.html-8e4e6651.js" as="script"><link rel="prefetch" href="/assets/主从复制原理.html-5692886d.js" as="script"><link rel="prefetch" href="/assets/内存淘汰策略.html-2108cb44.js" as="script"><link rel="prefetch" href="/assets/缓存击穿、穿透、雪崩.html-d043509c.js" as="script"><link rel="prefetch" href="/assets/过期键的删除策略.html-86aae1a3.js" as="script"><link rel="prefetch" href="/assets/NameServer.html-6a201ec6.js" as="script"><link rel="prefetch" href="/assets/消息发送.html-4164a815.js" as="script"><link rel="prefetch" href="/assets/消息持久化.html-ad4aec32.js" as="script"><link rel="prefetch" href="/assets/Spring4.3.x源码阅读.html-ce67a2f8.js" as="script"><link rel="prefetch" href="/assets/过滤器和拦截器的区别.html-c5dbe180.js" as="script"><link rel="prefetch" href="/assets/MySQL安装.html-ce91cf4e.js" as="script"><link rel="prefetch" href="/assets/nvm_node安装.html-c02c4943.js" as="script"><link rel="prefetch" href="/assets/xxl-job源码阅读.html-4a3b4336.js" as="script"><link rel="prefetch" href="/assets/算法补充题.html-82552fdf.js" as="script"><link rel="prefetch" href="/assets/转换函数.html-4e846421.js" as="script"><link rel="prefetch" href="/assets/分布式事务.html-4015f1f4.js" as="script"><link rel="prefetch" href="/assets/深入理解Java虚拟机.html-f58d9b73.js" as="script"><link rel="prefetch" href="/assets/Redis开发与运维.html-8fbbb19d.js" as="script"><link rel="prefetch" href="/assets/Redis设计与实现.html-3d2f28fe.js" as="script"><link rel="prefetch" href="/assets/中级开发（5年内） .html-fe02712d.js" as="script"><link rel="prefetch" href="/assets/初级开发（3年内）.html-669c578d.js" as="script"><link rel="prefetch" href="/assets/高级开发（8年内）.html-8e45d1a0.js" as="script"><link rel="prefetch" href="/assets/apache poi导致的oom.html-ac4f4186.js" as="script"><link rel="prefetch" href="/assets/Spring启动及扫描流程.html-dca86884.js" as="script"><link rel="prefetch" href="/assets/404.html-81a48005.js" as="script"><link rel="prefetch" href="/assets/index.html-2b6ad090.js" as="script"><link rel="prefetch" href="/assets/index.html-30245415.js" as="script"><link rel="prefetch" href="/assets/index.html-c2c088b3.js" as="script"><link rel="prefetch" href="/assets/index.html-f512ec67.js" as="script"><link rel="prefetch" href="/assets/index.html-e1891597.js" as="script"><link rel="prefetch" href="/assets/index.html-b77929dc.js" as="script"><link rel="prefetch" href="/assets/index.html-3e3e3234.js" as="script"><link rel="prefetch" href="/assets/index.html-db16fd88.js" as="script"><link rel="prefetch" href="/assets/index.html-02e59ce2.js" as="script"><link rel="prefetch" href="/assets/index.html-55c7e4fd.js" as="script"><link rel="prefetch" href="/assets/index.html-3b089dfb.js" as="script"><link rel="prefetch" href="/assets/index.html-ac5b8d61.js" as="script"><link rel="prefetch" href="/assets/index.html-a2f3ea26.js" as="script"><link rel="prefetch" href="/assets/index.html-b57e51cb.js" as="script"><link rel="prefetch" href="/assets/index.html-0c321714.js" as="script"><link rel="prefetch" href="/assets/index.html-6266e407.js" as="script"><link rel="prefetch" href="/assets/index.html-00f53358.js" as="script"><link rel="prefetch" href="/assets/index.html-7b46e405.js" as="script"><link rel="prefetch" href="/assets/index.html-219b1b1f.js" as="script"><link rel="prefetch" href="/assets/index.html-ecf569c4.js" as="script"><link rel="prefetch" href="/assets/aboutMe.html-a5377517.js" as="script"><link rel="prefetch" href="/assets/index.html-df94614a.js" as="script"><link rel="prefetch" href="/assets/Java集合.html-25980358.js" as="script"><link rel="prefetch" href="/assets/InnoDB的Buffer Pool.html-2608d2bb.js" as="script"><link rel="prefetch" href="/assets/InnoDB的行格式和页结构.html-b66829d8.js" as="script"><link rel="prefetch" href="/assets/MySQL中的各种锁.html-eda1f1e8.js" as="script"><link rel="prefetch" href="/assets/MySQL中的排序.html-155835e9.js" as="script"><link rel="prefetch" href="/assets/MySQL事务与MVCC.html-5203098d.js" as="script"><link rel="prefetch" href="/assets/MySQL优化器原理.html-580192b8.js" as="script"><link rel="prefetch" href="/assets/5种IO模型.html-e14d32e5.js" as="script"><link rel="prefetch" href="/assets/Netty的线程模型.html-79ec3f0d.js" as="script"><link rel="prefetch" href="/assets/粘包拆包问题.html-ad117100.js" as="script"><link rel="prefetch" href="/assets/AOF和RDB持久化.html-8f530297.js" as="script"><link rel="prefetch" href="/assets/Gossip协议.html-68ce5f80.js" as="script"><link rel="prefetch" href="/assets/Redisson看门狗机制.html-f806e761.js" as="script"><link rel="prefetch" href="/assets/主从复制原理.html-c35d3468.js" as="script"><link rel="prefetch" href="/assets/内存淘汰策略.html-58163b51.js" as="script"><link rel="prefetch" href="/assets/缓存击穿、穿透、雪崩.html-d8d78972.js" as="script"><link rel="prefetch" href="/assets/过期键的删除策略.html-b2be5f51.js" as="script"><link rel="prefetch" href="/assets/NameServer.html-6ada07a1.js" as="script"><link rel="prefetch" href="/assets/消息发送.html-08920331.js" as="script"><link rel="prefetch" href="/assets/消息持久化.html-32746c2c.js" as="script"><link rel="prefetch" href="/assets/Spring4.3.x源码阅读.html-3475e320.js" as="script"><link rel="prefetch" href="/assets/过滤器和拦截器的区别.html-56c13477.js" as="script"><link rel="prefetch" href="/assets/MySQL安装.html-c7803a3a.js" as="script"><link rel="prefetch" href="/assets/nvm_node安装.html-736601b9.js" as="script"><link rel="prefetch" href="/assets/xxl-job源码阅读.html-2b98669d.js" as="script"><link rel="prefetch" href="/assets/算法补充题.html-37debf9d.js" as="script"><link rel="prefetch" href="/assets/转换函数.html-44dfb333.js" as="script"><link rel="prefetch" href="/assets/分布式事务.html-e6634dbe.js" as="script"><link rel="prefetch" href="/assets/深入理解Java虚拟机.html-3810cea1.js" as="script"><link rel="prefetch" href="/assets/Redis开发与运维.html-76744132.js" as="script"><link rel="prefetch" href="/assets/Redis设计与实现.html-eccc56a7.js" as="script"><link rel="prefetch" href="/assets/中级开发（5年内） .html-a2fc4d16.js" as="script"><link rel="prefetch" href="/assets/初级开发（3年内）.html-e48a4104.js" as="script"><link rel="prefetch" href="/assets/高级开发（8年内）.html-9138cbdd.js" as="script"><link rel="prefetch" href="/assets/apache poi导致的oom.html-b39dacda.js" as="script"><link rel="prefetch" href="/assets/Spring启动及扫描流程.html-0b604fff.js" as="script"><link rel="prefetch" href="/assets/404.html-742f0851.js" as="script"><link rel="prefetch" href="/assets/index.html-160c66d3.js" as="script"><link rel="prefetch" href="/assets/index.html-bb7b1e80.js" as="script"><link rel="prefetch" href="/assets/index.html-116fe317.js" as="script"><link rel="prefetch" href="/assets/index.html-17f24df8.js" as="script"><link rel="prefetch" href="/assets/index.html-3fe5d15e.js" as="script"><link rel="prefetch" href="/assets/index.html-c894bb5e.js" as="script"><link rel="prefetch" href="/assets/index.html-25b31507.js" as="script"><link rel="prefetch" href="/assets/index.html-8bb87106.js" as="script"><link rel="prefetch" href="/assets/index.html-255fc5d5.js" as="script"><link rel="prefetch" href="/assets/index.html-373a73ca.js" as="script"><link rel="prefetch" href="/assets/index.html-d539e1a9.js" as="script"><link rel="prefetch" href="/assets/index.html-1475787c.js" as="script"><link rel="prefetch" href="/assets/index.html-8073adbe.js" as="script"><link rel="prefetch" href="/assets/index.html-2ff9332c.js" as="script"><link rel="prefetch" href="/assets/index.html-f14ceb6a.js" as="script"><link rel="prefetch" href="/assets/index.html-54082429.js" as="script"><link rel="prefetch" href="/assets/index.html-264a3de2.js" as="script"><link rel="prefetch" href="/assets/index.html-fb62746d.js" as="script"><link rel="prefetch" href="/assets/index.html-81d28dad.js" as="script"><link rel="prefetch" href="/assets/index.html-56062662.js" as="script"><link rel="prefetch" href="/assets/giscus-0b7adcf8.js" as="script"><link rel="prefetch" href="/assets/photoswipe.esm-1464cdb9.js" as="script"><link rel="prefetch" href="/assets/index-e32a7948.js" as="script">
  </head>
  <body>
    <div id="app"><!--[--><!--[--><!--[--><span tabindex="-1"></span><a href="#main-content" class="vp-skip-link sr-only">跳至主要內容</a><!--]--><!--[--><div class="theme-container has-toc"><!--[--><header id="navbar" class="vp-navbar"><div class="vp-navbar-start"><button type="button" class="vp-toggle-sidebar-button" title="Toggle Sidebar"><span class="icon"></span></button><!--[--><!----><!--]--><!--[--><a class="vp-link vp-brand vp-brand" href="/"><img class="vp-nav-logo" src="https://gitee.com/eddie-lucas/images/raw/master/img/logo.svg" alt="小刘Learning"><!----><span class="vp-site-name hide-in-pad">小刘Learning</span></a><!--]--><!--[--><!----><!--]--></div><div class="vp-navbar-center"><!--[--><!----><!--]--><!--[--><nav class="vp-nav-links"><div class="nav-item hide-in-mobile"><a aria-label="首页" class="vp-link nav-link nav-link" href="/"><span class="font-icon icon iconfont icon-shouye" style=""></span>首页<!----></a></div><div class="nav-item hide-in-mobile"><a aria-label="后端技术" class="vp-link nav-link nav-link" href="/backend/mysql/MySQL%E4%BA%8B%E5%8A%A1%E4%B8%8EMVCC.html"><span class="font-icon icon iconfont icon-xingqiu" style=""></span>后端技术<!----></a></div><div class="nav-item hide-in-mobile"><a aria-label="读书笔记" class="vp-link nav-link nav-link" href="/readNote/redis/Redis%E5%BC%80%E5%8F%91%E4%B8%8E%E8%BF%90%E7%BB%B4.html"><span class="font-icon icon iconfont icon-book" style=""></span>读书笔记<!----></a></div></nav><!--]--><!--[--><!----><!--]--></div><div class="vp-navbar-end"><!--[--><!----><!--]--><!--[--><!----><div class="nav-item vp-repo"><a class="vp-repo-link" href="https://github.com/eddie-lucas" target="_blank" rel="noopener noreferrer" aria-label="GitHub"><svg xmlns="http://www.w3.org/2000/svg" class="icon github-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="github icon" style="width:1.25rem;height:1.25rem;vertical-align:middle;"><path d="M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"></path></svg></a></div><div class="nav-item hide-in-mobile"><button type="button" id="appearance-switch"><svg xmlns="http://www.w3.org/2000/svg" class="icon auto-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="auto icon" style="display:block;"><path d="M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm0-840c-198.78 0-360 161.22-360 360 0 198.84 161.22 360 360 360s360-161.16 360-360c0-198.78-161.22-360-360-360zm0 660V212c165.72 0 300 134.34 300 300 0 165.72-134.28 300-300 300z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" class="icon dark-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="dark icon" style="display:none;"><path d="M524.8 938.667h-4.267a439.893 439.893 0 0 1-313.173-134.4 446.293 446.293 0 0 1-11.093-597.334A432.213 432.213 0 0 1 366.933 90.027a42.667 42.667 0 0 1 45.227 9.386 42.667 42.667 0 0 1 10.24 42.667 358.4 358.4 0 0 0 82.773 375.893 361.387 361.387 0 0 0 376.747 82.774 42.667 42.667 0 0 1 54.187 55.04 433.493 433.493 0 0 1-99.84 154.88 438.613 438.613 0 0 1-311.467 128z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" class="icon light-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="light icon" style="display:none;"><path d="M952 552h-80a40 40 0 0 1 0-80h80a40 40 0 0 1 0 80zM801.88 280.08a41 41 0 0 1-57.96-57.96l57.96-58a41.04 41.04 0 0 1 58 58l-58 57.96zM512 752a240 240 0 1 1 0-480 240 240 0 0 1 0 480zm0-560a40 40 0 0 1-40-40V72a40 40 0 0 1 80 0v80a40 40 0 0 1-40 40zm-289.88 88.08-58-57.96a41.04 41.04 0 0 1 58-58l57.96 58a41 41 0 0 1-57.96 57.96zM192 512a40 40 0 0 1-40 40H72a40 40 0 0 1 0-80h80a40 40 0 0 1 40 40zm30.12 231.92a41 41 0 0 1 57.96 57.96l-57.96 58a41.04 41.04 0 0 1-58-58l58-57.96zM512 832a40 40 0 0 1 40 40v80a40 40 0 0 1-80 0v-80a40 40 0 0 1 40-40zm289.88-88.08 58 57.96a41.04 41.04 0 0 1-58 58l-57.96-58a41 41 0 0 1 57.96-57.96z"></path></svg></button></div><!----><!--]--><!--[--><!----><!--]--><button type="button" class="vp-toggle-navbar-button" aria-label="Toggle Navbar" aria-expanded="false" aria-controls="nav-screen"><span><span class="vp-top"></span><span class="vp-middle"></span><span class="vp-bottom"></span></span></button></div></header><!----><!--]--><!----><div class="toggle-sidebar-wrapper"><span class="arrow start"></span></div><aside id="sidebar" class="vp-sidebar"><!--[--><!----><!--]--><ul class="vp-sidebar-links"><li><section class="vp-sidebar-group"><button class="vp-sidebar-heading clickable" type="button"><span class="font-icon icon iconfont icon-MySQL" style=""></span><span class="vp-sidebar-title">MySQL</span><span class="vp-arrow end"></span></button><!----></section></li><li><section class="vp-sidebar-group"><button class="vp-sidebar-heading clickable" type="button"><span class="font-icon icon iconfont icon-redis" style=""></span><span class="vp-sidebar-title">Redis</span><span class="vp-arrow end"></span></button><!----></section></li><li><section class="vp-sidebar-group"><button class="vp-sidebar-heading clickable" type="button"><span class="font-icon icon iconfont icon-Spring" style=""></span><span class="vp-sidebar-title">Spring</span><span class="vp-arrow end"></span></button><!----></section></li><li><section class="vp-sidebar-group"><button class="vp-sidebar-heading clickable active" type="button"><span class="font-icon icon iconfont icon-a-icons8-mybatis2" style=""></span><span class="vp-sidebar-title">MyBatis</span><span class="vp-arrow down"></span></button><ul class="vp-sidebar-links"><li><!--[--><a aria-label="MyBatis常见问题" class="vp-link nav-link active vp-sidebar-link vp-sidebar-page active nav-link active vp-sidebar-link vp-sidebar-page active" href="/backend/mybatis/MyBatis%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.html"><!---->MyBatis常见问题<!----></a><ul class="vp-sidebar-sub-headers"><li class="vp-sidebar-sub-header"><a aria-label="为什么mapper接口没有实现类还能调用？" class="vp-link nav-link vp-sidebar-link vp-heading nav-link vp-sidebar-link vp-heading" href="/backend/mybatis/MyBatis%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.html#为什么mapper接口没有实现类还能调用"><!---->为什么mapper接口没有实现类还能调用？<!----></a><ul class="vp-sidebar-sub-headers"></ul></li><li class="vp-sidebar-sub-header"><a aria-label="#和$的区别" class="vp-link nav-link vp-sidebar-link vp-heading nav-link vp-sidebar-link vp-heading" href="/backend/mybatis/MyBatis%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.html#和-的区别"><!---->#和$的区别<!----></a><ul class="vp-sidebar-sub-headers"></ul></li><li class="vp-sidebar-sub-header"><a aria-label="分步查询，延迟加载（懒加载）原理" class="vp-link nav-link vp-sidebar-link vp-heading nav-link vp-sidebar-link vp-heading" href="/backend/mybatis/MyBatis%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.html#分步查询-延迟加载-懒加载-原理"><!---->分步查询，延迟加载（懒加载）原理<!----></a><ul class="vp-sidebar-sub-headers"></ul></li><li class="vp-sidebar-sub-header"><a aria-label="缓存机制" class="vp-link nav-link vp-sidebar-link vp-heading nav-link vp-sidebar-link vp-heading" href="/backend/mybatis/MyBatis%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.html#缓存机制"><!---->缓存机制<!----></a><ul class="vp-sidebar-sub-headers"></ul></li><li class="vp-sidebar-sub-header"><a aria-label="拦截器Interceptor" class="vp-link nav-link vp-sidebar-link vp-heading nav-link vp-sidebar-link vp-heading" href="/backend/mybatis/MyBatis%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.html#拦截器interceptor"><!---->拦截器Interceptor<!----></a><ul class="vp-sidebar-sub-headers"></ul></li></ul><!--]--></li></ul></section></li><li><section class="vp-sidebar-group"><button class="vp-sidebar-heading clickable" type="button"><span class="font-icon icon iconfont icon-xxl-job" style=""></span><span class="vp-sidebar-title">XXL-JOB</span><span class="vp-arrow end"></span></button><!----></section></li><li><section class="vp-sidebar-group"><button class="vp-sidebar-heading clickable" type="button"><span class="font-icon icon iconfont icon-RocketMQ" style=""></span><span class="vp-sidebar-title">RocketMQ 4.6.0</span><span class="vp-arrow end"></span></button><!----></section></li><li><section class="vp-sidebar-group"><button class="vp-sidebar-heading clickable" type="button"><span class="font-icon icon iconfont icon-Netty" style=""></span><span class="vp-sidebar-title">Netty</span><span class="vp-arrow end"></span></button><!----></section></li><li><section class="vp-sidebar-group"><button class="vp-sidebar-heading clickable" type="button"><span class="font-icon icon iconfont icon-tools" style=""></span><span class="vp-sidebar-title">工具安装教程</span><span class="vp-arrow end"></span></button><!----></section></li></ul><!--[--><!----><!--]--></aside><!--[--><main id="main-content" class="vp-page"><!--[--><!--[--><!----><!--]--><!----><nav class="vp-breadcrumb disable"></nav><div class="vp-page-title"><h1><!---->MyBatis常见问题</h1><div class="page-info"><span class="page-author-info" aria-label="作者🖊" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon author-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="author icon"><path d="M649.6 633.6c86.4-48 147.2-144 147.2-249.6 0-160-128-288-288-288s-288 128-288 288c0 108.8 57.6 201.6 147.2 249.6-121.6 48-214.4 153.6-240 288-3.2 9.6 0 19.2 6.4 25.6 3.2 9.6 12.8 12.8 22.4 12.8h704c9.6 0 19.2-3.2 25.6-12.8 6.4-6.4 9.6-16 6.4-25.6-25.6-134.4-121.6-240-243.2-288z"></path></svg><span><span class="page-author-item">小刘Learning</span></span><span property="author" content="小刘Learning"></span></span><!----><span class="page-date-info" aria-label="写作日期📅" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon calendar-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="calendar icon"><path d="M716.4 110.137c0-18.753-14.72-33.473-33.472-33.473-18.753 0-33.473 14.72-33.473 33.473v33.473h66.993v-33.473zm-334.87 0c0-18.753-14.72-33.473-33.473-33.473s-33.52 14.72-33.52 33.473v33.473h66.993v-33.473zm468.81 33.52H716.4v100.465c0 18.753-14.72 33.473-33.472 33.473a33.145 33.145 0 01-33.473-33.473V143.657H381.53v100.465c0 18.753-14.72 33.473-33.473 33.473a33.145 33.145 0 01-33.473-33.473V143.657H180.6A134.314 134.314 0 0046.66 277.595v535.756A134.314 134.314 0 00180.6 947.289h669.74a134.36 134.36 0 00133.94-133.938V277.595a134.314 134.314 0 00-133.94-133.938zm33.473 267.877H147.126a33.145 33.145 0 01-33.473-33.473c0-18.752 14.72-33.473 33.473-33.473h736.687c18.752 0 33.472 14.72 33.472 33.473a33.145 33.145 0 01-33.472 33.473z"></path></svg><span><!----></span><meta property="datePublished" content="2024-08-16T09:13:59.000Z"></span><!----><span class="page-reading-time-info" aria-label="阅读时间⌛" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon timer-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="timer icon"><path d="M799.387 122.15c4.402-2.978 7.38-7.897 7.38-13.463v-1.165c0-8.933-7.38-16.312-16.312-16.312H256.33c-8.933 0-16.311 7.38-16.311 16.312v1.165c0 5.825 2.977 10.874 7.637 13.592 4.143 194.44 97.22 354.963 220.201 392.763-122.204 37.542-214.893 196.511-220.2 389.397-4.661 5.049-7.638 11.651-7.638 19.03v5.825h566.49v-5.825c0-7.379-2.849-13.981-7.509-18.9-5.049-193.016-97.867-351.985-220.2-389.527 123.24-37.67 216.446-198.453 220.588-392.892zM531.16 450.445v352.632c117.674 1.553 211.787 40.778 211.787 88.676H304.097c0-48.286 95.149-87.382 213.728-88.676V450.445c-93.077-3.107-167.901-81.297-167.901-177.093 0-8.803 6.99-15.793 15.793-15.793 8.803 0 15.794 6.99 15.794 15.793 0 80.261 63.69 145.635 142.01 145.635s142.011-65.374 142.011-145.635c0-8.803 6.99-15.793 15.794-15.793s15.793 6.99 15.793 15.793c0 95.019-73.789 172.82-165.96 177.093z"></path></svg><span>大约 11 分钟</span><meta property="timeRequired" content="PT11M"></span><!----><!----></div><hr></div><div class="toc-place-holder"><aside id="toc"><!--[--><!----><!--]--><div class="toc-header">此页内容<button type="button" class="print-button" title="打印"><svg xmlns="http://www.w3.org/2000/svg" class="icon print-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="print icon"><path d="M819.2 364.8h-44.8V128c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v236.8h-44.8C145.067 364.8 96 413.867 96 473.6v192c0 59.733 49.067 108.8 108.8 108.8h44.8V896c0 17.067 14.933 32 32 32h460.8c17.067 0 32-14.933 32-32V774.4h44.8c59.733 0 108.8-49.067 108.8-108.8v-192c0-59.733-49.067-108.8-108.8-108.8zM313.6 160h396.8v204.8H313.6V160zm396.8 704H313.6V620.8h396.8V864zM864 665.6c0 25.6-19.2 44.8-44.8 44.8h-44.8V588.8c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v121.6h-44.8c-25.6 0-44.8-19.2-44.8-44.8v-192c0-25.6 19.2-44.8 44.8-44.8h614.4c25.6 0 44.8 19.2 44.8 44.8v192z"></path></svg></button></div><div class="toc-wrapper"><ul class="toc-list"><!--[--><li class="toc-item"><a class="vp-link toc-link level2 toc-link level2" href="#为什么mapper接口没有实现类还能调用">为什么mapper接口没有实现类还能调用？</a></li><!----><!--]--><!--[--><li class="toc-item"><a class="vp-link toc-link level2 toc-link level2" href="#和-的区别">#和$的区别</a></li><!----><!--]--><!--[--><li class="toc-item"><a class="vp-link toc-link level2 toc-link level2" href="#分步查询-延迟加载-懒加载-原理">分步查询，延迟加载（懒加载）原理</a></li><!----><!--]--><!--[--><li class="toc-item"><a class="vp-link toc-link level2 toc-link level2" href="#缓存机制">缓存机制</a></li><!----><!--]--><!--[--><li class="toc-item"><a class="vp-link toc-link level2 toc-link level2" href="#拦截器interceptor">拦截器Interceptor</a></li><!----><!--]--></ul><div class="toc-marker" style="top:-1.7rem;"></div></div><!--[--><!----><!--]--></aside></div><!--[--><!----><!--]--><div class="theme-hope-content"><h2 id="为什么mapper接口没有实现类还能调用" tabindex="-1"><a class="header-anchor" href="#为什么mapper接口没有实现类还能调用" aria-hidden="true">#</a> 为什么mapper接口没有实现类还能调用？</h2><p>通常，在进行编码的时候，都是创建一个mapper接口和与之对应的xml文件，就能够在代码中直接进行调用，但是，通过接口进行调用的时候，本质上是调用接口的实现类的方法，但是mapper接口没有实现类，还能够调用，这是为什么呢？</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token annotation punctuation">@Mapper</span>
<span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">UserDao</span> <span class="token punctuation">{</span>
    <span class="token class-name">User</span> <span class="token function">getUserById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Param</span><span class="token punctuation">(</span><span class="token string">&quot;userId&quot;</span><span class="token punctuation">)</span> <span class="token class-name">String</span> userId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-xml line-numbers-mode" data-ext="xml"><pre class="language-xml"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>mapper</span> <span class="token attr-name">namespace</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>org.example.dao.UserDao<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>getUserById<span class="token punctuation">&quot;</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>string<span class="token punctuation">&quot;</span></span> <span class="token attr-name">resultType</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>org.example.entity.User<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
        select * from t_user where user_id = #{userId}
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>mapper</span><span class="token punctuation">&gt;</span></span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>带着这个问题，写了一个小demo</p><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/image-20240715190242438.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><p>可以看到，userDao是一个代理对象，本质上是通过jdk动态代理实现的，通过<code>MapperProxy#invoke</code>实现，这个代理对象知道我们的接口的全类名</p><p>在xml文件中，我们都有namespace属性，这个跟接口的全类名相互对应，因此就能够找到这个xml文件，另外，每个方法的方法名和xml文件中的每个语句的id一样，这样，就能够知道要调用哪个sql</p><p>因此xml文件就相当于这个接口的实现类，每个语句对应接口中的方法</p><h2 id="和-的区别" tabindex="-1"><a class="header-anchor" href="#和-的区别" aria-hidden="true">#</a> #和$的区别</h2><div class="language-xml line-numbers-mode" data-ext="xml"><pre class="language-xml"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>mapper</span> <span class="token attr-name">namespace</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>org.example.dao.UserDao<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>getUserById<span class="token punctuation">&quot;</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>string<span class="token punctuation">&quot;</span></span> <span class="token attr-name">resultType</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>org.example.entity.User<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
        select * from t_user where user_id = #{userId}
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>mapper</span><span class="token punctuation">&gt;</span></span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>先来看一下打印的日志，直观感受一下两个语句的区别，两个语句的区别就是把#换成$</p><div class="language-bash line-numbers-mode" data-ext="sh"><pre class="language-bash"><code><span class="token comment"># 这是使用#{}传参</span>
<span class="token operator">==</span><span class="token operator">&gt;</span>  Preparing: <span class="token keyword">select</span> * from t_user where user_id <span class="token operator">=</span> ?
<span class="token operator">==</span><span class="token operator">&gt;</span> Parameters: <span class="token number">12984</span><span class="token punctuation">(</span>String<span class="token punctuation">)</span>
<span class="token comment"># 这是使用${}传参</span>
<span class="token operator">==</span><span class="token operator">&gt;</span>  Preparing: <span class="token keyword">select</span> * from t_user where user_id <span class="token operator">=</span> <span class="token number">12984</span>
<span class="token operator">==</span><span class="token operator">&gt;</span> Parameters: 
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最直观的感受：</p><ul><li>使用#{}进行传参的时候，#{}会被替换成？，并且下面的Parameters有值</li><li>使用${}进行传参的时候，${}会被替换成变量的值，并且下面的Parameters没有内容</li></ul><p>对于#，经过层层debug，终于找到了设置参数的的地方：<code>ClientPreparedStatement</code></p><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/image-20240715204814767.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><p>由于我这个参数是string类型的，所以是调用的这个方法进行设置的，点击左侧的接口继承图标，可以找到父接口<code>PreparedStatement</code></p><p>结论：<strong>Mybatis在处理#{}的时候，会先替换成？，然后调用<code>PreparedStatement</code>的set方法来赋值，而${}，只是进行简单的替换</strong></p><p>#{}传参数可以有效的防止sql注入，提高系统的安全性</p><blockquote><p>既然#{}比${}更加安全，那是不是可以无脑全部都用#{}，那${}又有哪些应用场景？</p></blockquote><ul><li>动态表名</li><li>动态字段名</li><li>拼接sql片段</li></ul><p>因为如果表名，字段名这些是字符串，如果使用#{}去传参的话， 参数会被添加上单引号<code>&#39;&#39;</code>，就会变成：</p><div class="language-sql line-numbers-mode" data-ext="sql"><pre class="language-sql"><code><span class="token keyword">select</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">&#39;user&#39;</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>在进行查询的时候，就会出错了，我们希望的表名是没有单引号的，所以需要用${}，表名也是同样的道理</p><p>另外，上面的sql语句，我们传入的是字符串，但是执行的sql的参数却没有带<code>&#39;&#39;</code>，所以，<strong>在使用${}，需要单引号，则需要我们自己手动加上</strong></p><div class="language-bash line-numbers-mode" data-ext="sh"><pre class="language-bash"><code><span class="token comment"># 这是使用${}传参</span>
<span class="token operator">==</span><span class="token operator">&gt;</span>  Preparing: <span class="token keyword">select</span> * from t_user where user_id <span class="token operator">=</span> <span class="token number">12984</span>
<span class="token operator">==</span><span class="token operator">&gt;</span> Parameters: 

<span class="token comment"># 正确的sql</span>
<span class="token keyword">select</span> * from t_user where user_id <span class="token operator">=</span> <span class="token string">&#39;${userId}&#39;</span>
<span class="token comment"># 添加单引号后，打印出来的sql</span>
<span class="token keyword">select</span> * from t_user where user_id <span class="token operator">=</span> <span class="token string">&#39;12984&#39;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="分步查询-延迟加载-懒加载-原理" tabindex="-1"><a class="header-anchor" href="#分步查询-延迟加载-懒加载-原理" aria-hidden="true">#</a> 分步查询，延迟加载（懒加载）原理</h2><p>Mybatis支持延迟加载来进行按需查询，如果我们用不到一些数据，就不用查了，在需要这些数据的时候再进行查找，这样能够提高效率，节约内存空间，另外，分布查询将一个多表关联sql拆分成了多个sql，通过这种形式，减少了复杂sql的出现</p><p>[<strong>举例</strong>]一个对象account中有其他对象的引用user，有时候我们只想用对象account，如果关联查询，那么就都会查出来，这个时候可以考虑分步查询延迟加载，当需要用到user对象的时候再进行查找，不用的时候不查</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token annotation punctuation">@Data</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Account</span> <span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token class-name">Integer</span> id<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token class-name">Integer</span> userId<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token class-name">Integer</span> money<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token class-name">User</span> user<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Mybatis支持<code>association</code>和<code>collection</code>延迟加载，即一对一延迟加载和一对多延迟加载，要开启延迟加载功能，需要在<code>mybatis-config.xml</code>中添加：</p><div class="language-xml line-numbers-mode" data-ext="xml"><pre class="language-xml"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>setting</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>lazyLoadingEnabled<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">/&gt;</span></span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><blockquote><p>下面通过association来理解延迟加载的原理</p></blockquote><div class="language-xml line-numbers-mode" data-ext="xml"><pre class="language-xml"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>mapper</span> <span class="token attr-name">namespace</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>org.example.dao.AccountDao<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>userAccountMap<span class="token punctuation">&quot;</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>org.example.entity.Account<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>id</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>id<span class="token punctuation">&quot;</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>id<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>id</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>result</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>userId<span class="token punctuation">&quot;</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>user_id<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>result</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>result</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>money<span class="token punctuation">&quot;</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>money<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>result</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>association</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>user<span class="token punctuation">&quot;</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>user_id<span class="token punctuation">&quot;</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>org.example.entity.User<span class="token punctuation">&quot;</span></span> <span class="token attr-name">select</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>org.example.dao.UserDao.getUserById<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>association</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>resultMap</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>findAll<span class="token punctuation">&quot;</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>userAccountMap<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
        select * from account
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>mapper</span><span class="token punctuation">&gt;</span></span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>先来看看当只调用findAll</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Account</span><span class="token punctuation">&gt;</span></span> accountList <span class="token operator">=</span> accountDao<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/image-20240716111659993.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><p>可以看到，当只调用findAll，控制台只执行了一个sql，但是如果在代码中获取user，则会执行多个sql</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Account</span><span class="token punctuation">&gt;</span></span> accountList <span class="token operator">=</span> accountDao<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token class-name">Account</span> account<span class="token operator">:</span>accountList<span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>account<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/image-20240716111927165.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><blockquote><p>延迟加载的实现原理</p></blockquote><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/image-20240716150747558.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><p>首先，通过debug可以看到，我们的account对象其实是一个<strong>代理对象</strong>，并且是使用Javassist技术创建的，也就是说，当我们调用get方法的时候，其实是通过代理对象去调用的</p><p>然后找到核心代码<code>EnhancedResultObjectProxyImpl#invoke</code></p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token class-name">Object</span> <span class="token function">invoke</span><span class="token punctuation">(</span><span class="token class-name">Object</span> enhanced<span class="token punctuation">,</span> <span class="token class-name">Method</span> method<span class="token punctuation">,</span> <span class="token class-name">Method</span> methodProxy<span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Throwable</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>lazyLoader<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token constant">FINALIZE_METHOD</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>methodName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>aggressive <span class="token operator">||</span> lazyLoadTriggerMethods<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>methodName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            lazyLoader<span class="token punctuation">.</span><span class="token function">loadAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">PropertyNamer</span><span class="token punctuation">.</span><span class="token function">isSetter</span><span class="token punctuation">(</span>methodName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment">//如果调用的是setter方法</span>
            <span class="token keyword">final</span> <span class="token class-name">String</span> property <span class="token operator">=</span> <span class="token class-name">PropertyNamer</span><span class="token punctuation">.</span><span class="token function">methodToProperty</span><span class="token punctuation">(</span>methodName<span class="token punctuation">)</span><span class="token punctuation">;</span>
            lazyLoader<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>property<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">PropertyNamer</span><span class="token punctuation">.</span><span class="token function">isGetter</span><span class="token punctuation">(</span>methodName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment">//如果调用的是getter方法</span>
            <span class="token keyword">final</span> <span class="token class-name">String</span> property <span class="token operator">=</span> <span class="token class-name">PropertyNamer</span><span class="token punctuation">.</span><span class="token function">methodToProperty</span><span class="token punctuation">(</span>methodName<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>lazyLoader<span class="token punctuation">.</span><span class="token function">hasLoader</span><span class="token punctuation">(</span>property<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                lazyLoader<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span>property<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> methodProxy<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>enhanced<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote><p>代理对象是什么时候创建的？</p></blockquote><p>在<code>DefaultResultSetHandler</code>中，当我们为结果做映射的时候，如果发现有属性是懒加载的，就会创建代理对象</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token class-name">Object</span> <span class="token function">createResultObject</span><span class="token punctuation">(</span><span class="token class-name">ResultSetWrapper</span> rsw<span class="token punctuation">,</span> <span class="token class-name">ResultMap</span> resultMap<span class="token punctuation">,</span> <span class="token class-name">ResultLoaderMap</span> lazyLoader<span class="token punctuation">,</span>
                                  <span class="token class-name">String</span> columnPrefix<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">SQLException</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>useConstructorMappings <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment">// reset previous mapping result</span>
    <span class="token keyword">final</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Class</span><span class="token punctuation">&lt;</span><span class="token operator">?</span><span class="token punctuation">&gt;</span><span class="token punctuation">&gt;</span></span> constructorArgTypes <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">final</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span> constructorArgs <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">Object</span> resultObject <span class="token operator">=</span> <span class="token function">createResultObject</span><span class="token punctuation">(</span>rsw<span class="token punctuation">,</span> resultMap<span class="token punctuation">,</span> constructorArgTypes<span class="token punctuation">,</span> constructorArgs<span class="token punctuation">,</span> columnPrefix<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>resultObject <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token function">hasTypeHandlerForResultObject</span><span class="token punctuation">(</span>rsw<span class="token punctuation">,</span> resultMap<span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">final</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ResultMapping</span><span class="token punctuation">&gt;</span></span> propertyMappings <span class="token operator">=</span> resultMap<span class="token punctuation">.</span><span class="token function">getPropertyResultMappings</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//遍历resultmap映射</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">ResultMapping</span> propertyMapping <span class="token operator">:</span> propertyMappings<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">// 如果属性是懒加载的，就创建代理对象</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>propertyMapping<span class="token punctuation">.</span><span class="token function">getNestedQueryId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> propertyMapping<span class="token punctuation">.</span><span class="token function">isLazy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                resultObject <span class="token operator">=</span> configuration<span class="token punctuation">.</span><span class="token function">getProxyFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">createProxy</span><span class="token punctuation">(</span>resultObject<span class="token punctuation">,</span> lazyLoader<span class="token punctuation">,</span> configuration<span class="token punctuation">,</span>
                                                                           objectFactory<span class="token punctuation">,</span> constructorArgTypes<span class="token punctuation">,</span> constructorArgs<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">break</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>useConstructorMappings <span class="token operator">=</span> resultObject <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>constructorArgTypes<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
    <span class="token keyword">return</span> resultObject<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>总结：延迟加载的原理就是通过代理技术创建目标对象的代理对象（account），当要使用到懒加载对象(user)的时候，发现为null，就会执行事先定义好的sql（association标签中的select属性），将懒加载对象查询出来，然后代理对象调用set方法进行设置，这样，代理对象内部的懒加载对象就有值了，就能够正常使用了</p><h2 id="缓存机制" tabindex="-1"><a class="header-anchor" href="#缓存机制" aria-hidden="true">#</a> 缓存机制</h2><ul><li><strong>一级缓存LocalCache</strong></li></ul><p>在应用程序的运行过程中，我们有可能在一次数据库会话中，执行多次查询条件完全相同的sql，mybatis提供了一级缓存来优化这种场景，如果是相同的sql，会优先命中一级缓存，避免直接对数据库进行查询，提高性能</p><p>每个sqlSession中持有executor，每个executor中有一个localCache，当用户发起查询时，mybatis根据当前执行的语句生成MappedStatement，在localCache进行查询，如果缓存命中，直接返回结果，如果缓存没有命中，查询数据库，缓存结果写入到localCache，最后返回结果</p><ol><li>一级缓存的生命周期和sqlSession一致</li><li>一级缓存设计简单，是一个没有容量限制的hashMap</li><li>一级缓存的最大范围是sqlSession内部，有多个sqlSession或者是分布式环境下，数据库的写操作会引起脏数据，建议设定缓存级别为Statement</li></ol><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/mybatis-001.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><p>MyBatis的一级缓存默认是开启的，先来看看一级缓存的效果：</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token class-name">InputStream</span> inputStream <span class="token operator">=</span> <span class="token class-name">Resources</span><span class="token punctuation">.</span><span class="token function">getResourceAsStream</span><span class="token punctuation">(</span><span class="token string">&quot;mybatis-config.xml&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">SqlSessionFactory</span> sqlSessionFactory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SqlSessionFactoryBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span>inputStream<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">SqlSession</span> sqlSession <span class="token operator">=</span> sqlSessionFactory<span class="token punctuation">.</span><span class="token function">openSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">UserDao</span> userDao <span class="token operator">=</span> sqlSession<span class="token punctuation">.</span><span class="token function">getMapper</span><span class="token punctuation">(</span><span class="token class-name">UserDao</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//两次相同的查询</span>
userDao<span class="token punctuation">.</span><span class="token function">getUserById</span><span class="token punctuation">(</span><span class="token number">12984</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
userDao<span class="token punctuation">.</span><span class="token function">getUserById</span><span class="token punctuation">(</span><span class="token number">12984</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在上面的代码中，同一个sqlSession，然后两次相同的查询，对于第一次查询，debug发现查询的数据库</p><p>对于第二次查询，debug发现，会从localCache中获取数据，而不查询数据库</p><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/image-20240717105814773.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><p>当从数据库中查询到数据之后，会将数据添加到一级缓存中，下次查找的时候，会直接从缓存中获取</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">E</span><span class="token punctuation">&gt;</span></span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">E</span><span class="token punctuation">&gt;</span></span> <span class="token function">queryFromDatabase</span><span class="token punctuation">(</span><span class="token class-name">MappedStatement</span> ms<span class="token punctuation">,</span> <span class="token class-name">Object</span> parameter<span class="token punctuation">,</span> <span class="token class-name">RowBounds</span> rowBounds<span class="token punctuation">,</span>
                                      <span class="token class-name">ResultHandler</span> resultHandler<span class="token punctuation">,</span> <span class="token class-name">CacheKey</span> key<span class="token punctuation">,</span> <span class="token class-name">BoundSql</span> boundSql<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">SQLException</span> <span class="token punctuation">{</span>
    
    
    <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">E</span><span class="token punctuation">&gt;</span></span> list<span class="token punctuation">;</span>
    <span class="token comment">//先在localCache中占个位置</span>
    localCache<span class="token punctuation">.</span><span class="token function">putObject</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token constant">EXECUTION_PLACEHOLDER</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">//从数据库中查数据</span>
        list <span class="token operator">=</span> <span class="token function">doQuery</span><span class="token punctuation">(</span>ms<span class="token punctuation">,</span> parameter<span class="token punctuation">,</span> rowBounds<span class="token punctuation">,</span> resultHandler<span class="token punctuation">,</span> boundSql<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        <span class="token comment">//移除localCache中的占位符</span>
        localCache<span class="token punctuation">.</span><span class="token function">removeObject</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">//放入从数据库中查到的数据</span>
    localCache<span class="token punctuation">.</span><span class="token function">putObject</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> list<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>ms<span class="token punctuation">.</span><span class="token function">getStatementType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">StatementType</span><span class="token punctuation">.</span><span class="token constant">CALLABLE</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        localOutputParameterCache<span class="token punctuation">.</span><span class="token function">putObject</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> parameter<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> list<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote><p>怎么判断是两次完全相同的查询?什么情况下会命中一级缓存？</p></blockquote><p>localCache本质上是hashMap，也就是说，只要key相同，就认为是两次相同的查询，这个key，每次进行查询的时候都会创建</p><p>查看key的创建方法，可以发现，<strong>要命中一级缓存中的数据，要满足这些条件</strong>：</p><ol><li>MappedStatement的id相同，这个也就是写sql的xml文件中，每个标签都要带的id（同一个mapper文件，同一个方法）</li><li>查询数据的起止范围相同</li><li>sql语句相同</li><li>参数相同（包括参数类型，参数的值）</li><li>环境相同</li><li>两次查询之间不能执行update，delete，insert方法，数据变动了，缓存当然不能使用，缓存也会被清空</li></ol><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token comment">//删除了一些影响阅读的源码后的createCacheKey方法</span>
<span class="token keyword">public</span> <span class="token class-name">CacheKey</span> <span class="token function">createCacheKey</span><span class="token punctuation">(</span><span class="token class-name">MappedStatement</span> ms<span class="token punctuation">,</span> <span class="token class-name">Object</span> parameterObject<span class="token punctuation">,</span> <span class="token class-name">RowBounds</span> rowBounds<span class="token punctuation">,</span> <span class="token class-name">BoundSql</span> boundSql<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token class-name">CacheKey</span> cacheKey <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CacheKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    cacheKey<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>ms<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    cacheKey<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>rowBounds<span class="token punctuation">.</span><span class="token function">getOffset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    cacheKey<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>rowBounds<span class="token punctuation">.</span><span class="token function">getLimit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    cacheKey<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>boundSql<span class="token punctuation">.</span><span class="token function">getSql</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ParameterMapping</span><span class="token punctuation">&gt;</span></span> parameterMappings <span class="token operator">=</span> boundSql<span class="token punctuation">.</span><span class="token function">getParameterMappings</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">ParameterMapping</span> parameterMapping <span class="token operator">:</span> parameterMappings<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        cacheKey<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>configuration<span class="token punctuation">.</span><span class="token function">getEnvironment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      cacheKey<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>configuration<span class="token punctuation">.</span><span class="token function">getEnvironment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> cacheKey<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul><li><strong>二级缓存</strong></li></ul><p>一级缓存不支持多个sqlSession之间共享，如果要实现共享，就需要二级缓存，开启二级缓存之后，在进入一级缓存的查询流程前，先在<code>CachingExecutor</code>进行二级缓存的查询</p><p>二级缓存开启后，同一个namespace下的所有操作语句，都影响着同一个cache，即二级缓存被多个sqlSession共享，是一个全局变量</p><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/image-20240718163447997.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><p>开启二级缓存后，查询流程：二级缓存---一级缓存---数据库</p><p>[<strong>注意</strong>]Mybatis的二级缓存在多表查询的时候极大可能会出现脏数据，有设计上的缺陷，另外Mybatis自带的二级缓存是但服务器进行工作的，无法实现分布式缓存，所以在分布式环境下，更推荐直接使用Redis，现在的互联网基本上都是分布式微服务的，所以不做过多了解</p><h2 id="拦截器interceptor" tabindex="-1"><a class="header-anchor" href="#拦截器interceptor" aria-hidden="true">#</a> 拦截器Interceptor</h2><p>mybatis有一个强大的功能，在执行过程中，能够拦截方法，执行一些增强逻辑，通过实现Interceptor接口，诞生了很多强大的插件，比如分页插件，动态表名插件，非法sql拦截插件...</p><p>这些插件的实现原理都是一样的，只不过这些插件增强的功能各不相同，现在通过实现一个统计sql执行时间的拦截器，来理解底层的原理</p><p>根据官网的说法，想要自定义插件，只需实现Interceptor接口，并指定想要拦截的方法签名即可</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token comment">//这里写拦截哪个方法，拦截的是Executor的query方法</span>
<span class="token annotation punctuation">@Intercepts</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token annotation punctuation">@Signature</span><span class="token punctuation">(</span>type <span class="token operator">=</span> <span class="token class-name">Executor</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span>method <span class="token operator">=</span> <span class="token string">&quot;query&quot;</span><span class="token punctuation">,</span>args <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token class-name">MappedStatement</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">RowBounds</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">ResultHandler</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TimeInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">Interceptor</span> <span class="token punctuation">{</span>
    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token class-name">Object</span> <span class="token function">intercept</span><span class="token punctuation">(</span><span class="token class-name">Invocation</span> invocation<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Throwable</span> <span class="token punctuation">{</span>
        <span class="token keyword">long</span> startTime <span class="token operator">=</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">Object</span> result <span class="token operator">=</span> invocation<span class="token punctuation">.</span><span class="token function">proceed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">long</span> endTime <span class="token operator">=</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">long</span> timeTotal <span class="token operator">=</span> endTime<span class="token operator">-</span>startTime<span class="token punctuation">;</span>
        <span class="token class-name">MappedStatement</span> mappedStatement <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">MappedStatement</span><span class="token punctuation">)</span> invocation<span class="token punctuation">.</span><span class="token function">getArgs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token class-name">String</span> sqlId <span class="token operator">=</span> mappedStatement<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&quot;执行SQL:&quot;</span><span class="token operator">+</span>sqlId<span class="token operator">+</span><span class="token string">&quot;,耗时:&quot;</span><span class="token operator">+</span>timeTotal<span class="token operator">+</span><span class="token string">&quot;ms&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> result<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">//未做任何重写</span>
    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token class-name">Object</span> <span class="token function">plugin</span><span class="token punctuation">(</span><span class="token class-name">Object</span> target<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token class-name">Interceptor</span><span class="token punctuation">.</span><span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">plugin</span><span class="token punctuation">(</span>target<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">//未做任何重写</span>
    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setProperties</span><span class="token punctuation">(</span><span class="token class-name">Properties</span> properties<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token class-name">Interceptor</span><span class="token punctuation">.</span><span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">setProperties</span><span class="token punctuation">(</span>properties<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token comment">//添加在mybatis-config.xml中</span>
<span class="token generics"><span class="token punctuation">&lt;</span>plugins<span class="token punctuation">&gt;</span></span>
    <span class="token operator">&lt;</span>plugin interceptor<span class="token operator">=</span><span class="token string">&quot;org.example.interceptor.TimeInterceptor&quot;</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>plugin<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>plugins<span class="token operator">&gt;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="https://gitee.com/eddie-lucas/images/raw/master/img/image-20240717200326241.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure><p>发现成功打印了执行时间，现在通过debug来了解底层原理</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span><span class="token punctuation">{</span>
    <span class="token class-name">InputStream</span> inputStream <span class="token operator">=</span> <span class="token class-name">Resources</span><span class="token punctuation">.</span><span class="token function">getResourceAsStream</span><span class="token punctuation">(</span><span class="token string">&quot;mybatis-config.xml&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//解析配置文件，创建sqlSessionFactory</span>
    <span class="token class-name">SqlSessionFactory</span> sqlSessionFactory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SqlSessionFactoryBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span>inputStream<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//打开sqlSession</span>
    <span class="token class-name">SqlSession</span> sqlSession <span class="token operator">=</span> sqlSessionFactory<span class="token punctuation">.</span><span class="token function">openSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">UserDao</span> userDao <span class="token operator">=</span> sqlSession<span class="token punctuation">.</span><span class="token function">getMapper</span><span class="token punctuation">(</span><span class="token class-name">UserDao</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    userDao<span class="token punctuation">.</span><span class="token function">getUserById</span><span class="token punctuation">(</span><span class="token number">12984</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>首先，当mybatis解析mybatis-config.xml的时候，会将所有的插件添加到一个List中</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">pluginsElement</span><span class="token punctuation">(</span><span class="token class-name">XNode</span> context<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>context <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">XNode</span> child <span class="token operator">:</span> context<span class="token punctuation">.</span><span class="token function">getChildren</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">//解析插件</span>
            <span class="token class-name">String</span> interceptor <span class="token operator">=</span> child<span class="token punctuation">.</span><span class="token function">getStringAttribute</span><span class="token punctuation">(</span><span class="token string">&quot;interceptor&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token class-name">Properties</span> properties <span class="token operator">=</span> child<span class="token punctuation">.</span><span class="token function">getChildrenAsProperties</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">//创建插件对象</span>
            <span class="token class-name">Interceptor</span> interceptorInstance <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">Interceptor</span><span class="token punctuation">)</span> <span class="token function">resolveClass</span><span class="token punctuation">(</span>interceptor<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getDeclaredConstructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            interceptorInstance<span class="token punctuation">.</span><span class="token function">setProperties</span><span class="token punctuation">(</span>properties<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">//将插件放到一个list中</span>
            configuration<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span>interceptorInstance<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当打开sqlSession的时候，会创建Executor，如果有针对executor实现插件，<strong>创建出来的executor其实是一个代理对象</strong></p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token comment">//defaultSqlSessionFactory.openSession()实际上调用的内部的这个方法</span>
<span class="token keyword">private</span> <span class="token class-name">SqlSession</span> <span class="token function">openSessionFromDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">//创建executor</span>
    <span class="token keyword">final</span> <span class="token class-name">Executor</span> executor <span class="token operator">=</span> configuration<span class="token punctuation">.</span><span class="token function">newExecutor</span><span class="token punctuation">(</span>tx<span class="token punctuation">,</span> execType<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token class-name">Executor</span> <span class="token function">newExecutor</span><span class="token punctuation">(</span><span class="token class-name">Transaction</span> transaction<span class="token punctuation">,</span> <span class="token class-name">ExecutorType</span> executorType<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">//如果有针对executor实现插件，最后都会返回代理对象，从而实现插件的增强功能</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token class-name">Executor</span><span class="token punctuation">)</span> interceptorChain<span class="token punctuation">.</span><span class="token function">pluginAll</span><span class="token punctuation">(</span>executor<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">//创建代理对象</span>
<span class="token class-name">Proxy</span><span class="token punctuation">.</span><span class="token function">newProxyInstance</span><span class="token punctuation">(</span>type<span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> interfaces<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Plugin</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> interceptor<span class="token punctuation">,</span> signatureMap<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样，当executor执行query方法的时候，就会执行代理逻辑，就能够执行自定义拦截器中的方法</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token comment">//当executor执行query方法的时候，会被代理</span>
<span class="token keyword">public</span> <span class="token class-name">Object</span> <span class="token function">invoke</span><span class="token punctuation">(</span><span class="token class-name">Object</span> proxy<span class="token punctuation">,</span> <span class="token class-name">Method</span> method<span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Throwable</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Method</span><span class="token punctuation">&gt;</span></span> methods <span class="token operator">=</span> signatureMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>method<span class="token punctuation">.</span><span class="token function">getDeclaringClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//有配置对应的拦截器，会执行拦截器的intercept方法</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>methods <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> methods<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>method<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">return</span> interceptor<span class="token punctuation">.</span><span class="token function">intercept</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Invocation</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> method<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">return</span> method<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">throw</span> <span class="token class-name">ExceptionUtil</span><span class="token punctuation">.</span><span class="token function">unwrapThrowable</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>总结：mybatis的拦截器的实现是通过代理技术实现的，当针对目标对象添加了拦截器之后，创建的实际上是代理对象，当执行要拦截的方法时，就会执行增强的代理逻辑</p></div><!--[--><!----><!--]--><footer class="page-meta"><div class="meta-item edit-link"><a href="https://github.com/eddie-lucas/edit/main/src/backend/mybatis/MyBatis常见问题.md" rel="noopener noreferrer" target="_blank" aria-label="编辑此页" class="nav-link label"><!--[--><svg xmlns="http://www.w3.org/2000/svg" class="icon edit-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="edit icon"><path d="M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"></path><path d="M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"></path></svg><!--]-->编辑此页<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span><!----></a></div><div class="meta-item git-info"><div class="update-time"><span class="label">上次编辑于: </span><!----></div><div class="contributors"><span class="label">贡献者: </span><!--[--><!--[--><span class="contributor" title="email: 761404955@qq.com">Lucas</span><!--]--><!--]--></div></div></footer><!----><div id="comment" class="giscus-wrapper input-top" style="display:none;"><div class="loading-icon-wrapper" style="display:flex;align-items:center;justify-content:center;height:96px"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" preserveAspectRatio="xMidYMid" viewBox="25 25 50 50"><animateTransform attributeName="transform" type="rotate" dur="2s" keyTimes="0;1" repeatCount="indefinite" values="0;360"></animateTransform><circle cx="50" cy="50" r="20" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round"><animate attributeName="stroke-dasharray" dur="1.5s" keyTimes="0;0.5;1" repeatCount="indefinite" values="1,200;90,200;1,200"></animate><animate attributeName="stroke-dashoffset" dur="1.5s" keyTimes="0;0.5;1" repeatCount="indefinite" values="0;-35px;-125px"></animate></circle></svg></div></div><!--[--><!----><!--]--><!--]--></main><!--]--><footer class="vp-footer-wrapper"><div class="vp-footer">记录，分享自己的学习过程.....</div><div class="vp-copyright">Copyright © 2025 小刘Learning</div></footer></div><!--]--><!--]--><!----><!----><!--]--></div>
    <script type="module" src="/assets/app-d83e1369.js" defer></script>
  </body>
</html>
